package com.school.information.controller;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.school.information.core.constant.TokenConstant;
import com.school.information.core.exception.ServiceException;
import com.school.information.core.security.authen.WechatAppAuthenticationToken;
import com.school.information.core.security.entity.SecurityUser;
import com.school.information.core.security.entity.WechatAppUser;
import com.school.information.core.service.RedisService;
import com.school.information.core.wechat.constant.WeChatConstant;
import com.school.information.core.wechat.properties.WeChatAppProperties;
import com.school.information.entity.SysUserEntity;
import com.school.information.entity.SysWechatUserEntity;
import com.school.information.enums.result.SysResultEnum;
import com.school.information.service.SysWechatUserService;
import com.school.information.utils.JwtUtil;
import com.school.information.utils.RSAUtil;
import com.school.information.utils.RandomIdUtil;
import com.school.information.utils.ResultUtil;
import com.school.information.vo.LoginVO;
import com.school.information.vo.TokenVO;
import com.wf.captcha.SpecCaptcha;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 用户登录token处理，及注销
 *
 * @author yuchuanchuan
 * @date 2020-12-07
 */
@Slf4j
@RestController
@RequestMapping("token")
@RequiredArgsConstructor
public class TokenController {
    private final WeChatAppProperties weChatAppProperties;
    private final RedisService redisService;
    private final AuthenticationManager authenticationManager;
    private final SysWechatUserService sysWechatUserService;


    @GetMapping("/test")
    public String test() {
        return "ceshi";
    }

    /**
     * 生成验证码
     *
     * @param request
     * @param response
     * @throws Exception
     */
    @GetMapping("/captcha")
    public ResultUtil captcha(@RequestParam("code") String code, HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 防止重复刷验证码，导致redis缓存过多。同时也防止没有过期的旧的验证码进行验证
        if (StringUtils.isNotBlank(code)) {
            // 判断redis中是否存在 验证码
            if (redisService.hasKey(code)) {
                // 存在则在redis中移除
                redisService.del(code);
            }
        }
//        ArithmeticCaptcha arithmeticCaptcha = new ArithmeticCaptcha(110, 38, 2);
//        String captchaValue = arithmeticCaptcha.text();
        SpecCaptcha captcha = new SpecCaptcha(110, 38, 4);
        String captchaValue = captcha.text();// 获取验证码的字符
        String uuid = RandomIdUtil.getUUID();
        redisService.set(uuid, captchaValue, TokenConstant.CODE_EXPIRES_IN);
        // 验证码信息
        Map<String, Object> imgResult = new HashMap<String, Object>(2) {{
            put("img", captcha.toBase64());
            put("uuid", uuid);
        }};
        return ResultUtil.success(imgResult);
    }

    /**
     * 后台登录功能
     *
     * @return
     */
    @PostMapping("/login")
    public ResultUtil login(@Valid @RequestBody LoginVO loginVO) throws Exception {
        log.info("===============进入pc端后端管理认证 用户名和密码的登录方式============");
        log.info("##loginVO={}", loginVO);
        if (Objects.isNull(loginVO)) {
            log.error("参数错误");
            return ResultUtil.ERROR_ARG;
        }
        if (StringUtils.isBlank(loginVO.getUuid()) || !redisService.hasKey(loginVO.getUuid())) {
            log.error("验证码不存在");
            return ResultUtil.error(SysResultEnum.INVALID_CAPTCHA);
        }
        // 验证验证码是否正确
        if (!loginVO.getCode().trim().equalsIgnoreCase((String) redisService.get(loginVO.getUuid()))) {
            log.error("验证码错误");
            return ResultUtil.error(SysResultEnum.ERROR_CAPTCHA);
        }

        Authentication authenticate = null;
        try {
            // 通过UsernamePasswordAuthenticationToken获取用户名和密码
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                    loginVO.getUserName(), RSAUtil.decrypt(loginVO.getPassword()));
            // AuthenticationManager委托机制对authenticationToken 进行用户认证  去调用UserDetailsServiceImpl.loadUserByUsername
            authenticate = authenticationManager.authenticate(authenticationToken);
        } catch (Exception e) {
            log.error("##登录认证失败:{}", e.getMessage());
            throw new ServiceException(SysResultEnum.USER_NAME_PASSWORD_ERROR);
        } finally {
            SecurityContextHolder.clearContext();
        }
        // 如果认证通过，使用user生成jwt jwt存入ResultUtil 返回
        TokenVO tokenVO = new TokenVO();
        // 如果认证通过，拿到这个当前登录用户信息
        SecurityUser securityUser = (SecurityUser) authenticate.getPrincipal();
        // 获取当前用户的userid
        SysUserEntity sysUser = securityUser.getSysUser();
        Map<String, Object> map = new HashMap<>(6);
        map.put(TokenConstant.LOGIN_DEVICE, TokenConstant.LOGIN_DEVICE_PC);
        map.put(TokenConstant.JWT_USER_ID, sysUser.getId().toString());
        map.put(TokenConstant.JWT_USER_PHONE, sysUser.getPhone());
        map.put(TokenConstant.JWT_USER_NAME, sysUser.getRealName());
        map.put(TokenConstant.JWT_USER_PERMS, securityUser.getPermissions());
        map.put(TokenConstant.JWT_USER_ROLES, securityUser.getRoles());
        String access_token = JwtUtil.createJwt(map, TokenConstant.EXPIRES_IN);
        String refresh_token = JwtUtil.createJwt(map, TokenConstant.REFRESH_EXPIRES_IN);
        // 把完整的用户信息存入redis userid为key 用户信息为value
        redisService.set(TokenConstant.LOGIN_USER_REDIS_KEY + TokenConstant.LOGIN_DEVICE_PC + "_" + sysUser.getId() + "_" + sysUser.getPhone(), securityUser, TokenConstant.EXPIRES_IN);
        tokenVO.setAccess_token(access_token).setRefresh_token(refresh_token).setExpires_in(TokenConstant.EXPIRES_IN);
        return ResultUtil.success(tokenVO);
    }


    /**
     * 退出 注销用户
     *
     * @return
     */
    @PostMapping("/logout")
    public ResultUtil logout(@RequestParam("accessToken") String accessToken, @RequestParam("refreshToken") String refreshToken) {
        // 清除redis中的token
        if (redisService.hasKey(TokenConstant.ACCESS_TOKEN + ":" + accessToken)) {
            redisService.del(TokenConstant.ACCESS_TOKEN + ":" + accessToken);
        }
        if (redisService.hasKey(TokenConstant.REFRESH_TOKEN + ":" + refreshToken)) {
            redisService.del(TokenConstant.REFRESH_TOKEN + ":" + refreshToken);
        }
        return ResultUtil.SUCCESS_NO_DATA;
    }

    /**
     * 微信小程序根据wx.login获取openid、unionid、session_key
     *
     * @param code
     * @return
     * @throws Exception
     */
    @GetMapping("wechat/login/{code}")
    public ResultUtil weChatAppLogin(@PathVariable("code") String code) throws Exception {
        log.info("===============进入微信小程序登录认证============");
        Map<String, Object> param = new HashMap<>(4);
        param.put("appid", weChatAppProperties.getAppId());
        param.put("secret", weChatAppProperties.getSecret());
        param.put("js_code", code);
        param.put("grant_type", "authorization_code");
        String result = HttpUtil.get(WeChatConstant.WECHAT_APP_SESSION, param);
        log.info("##result={}", result);
        cn.hutool.json.JSONObject jsonObject = JSONUtil.parseObj(result);
        Map<String, Object> map = JSONUtil.toBean(jsonObject, Map.class);
        // 如果返回正确 返回的信息中不带errcode和errmsg信息  返回错误会带有errcode和errmsg信息
        if (ObjectUtil.isEmpty(map.get("errcode"))) {
            String openid = map.get("openid").toString();
            // unionid 必须开通微信公众平台及公众号才会显示
            String unionid = ObjectUtil.isNotEmpty(map.get("unionid")) ? map.get("unionid").toString() : "";
            String sessionKey = map.get("session_key").toString();
            // 将信息存放到数据库中
            SysWechatUserEntity sysWechatUser = sysWechatUserService.getByOpenid(openid);
            if (ObjectUtil.isEmpty(sysWechatUser)) {
                sysWechatUser = new SysWechatUserEntity();
                sysWechatUser.setOpenid(openid);
                sysWechatUser.setUnionid(unionid);
                sysWechatUserService.save(sysWechatUser);
            }

            Authentication authenticate = null;
            try {
                // 通过UsernamePasswordAuthenticationToken获取用户名和密码
                WechatAppAuthenticationToken authenticationToken = new WechatAppAuthenticationToken(openid, sessionKey);
                // AuthenticationManager委托机制对authenticationToken 进行用户认证  去调用UserDetailsServiceImpl.loadUserByUsername
                authenticate = authenticationManager.authenticate(authenticationToken);

//                // 通过UsernamePasswordAuthenticationToken获取用户名和密码
//                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
//                        openid, null);
//                // AuthenticationManager委托机制对authenticationToken 进行用户认证  去调用UserDetailsServiceImpl.loadUserByUsername
//                authenticate = authenticationManager.authenticate(authenticationToken);
            } catch (Exception e) {
                log.error("##登录认证失败:{}", e.getMessage());
                throw new ServiceException(SysResultEnum.WECHAT_USER_UPDATE_LOGIN_FAIL);
            } finally {
                SecurityContextHolder.clearContext();
            }
            // 如果认证通过，拿到这个当前登录用户信息
            WechatAppUser wechatAppUser = (WechatAppUser) authenticate.getPrincipal();
            wechatAppUser.setSessionKey(sessionKey);

            TokenVO tokenVO = new TokenVO();
            String currentTime = System.currentTimeMillis() + "";
            Map<String, Object> tokenMap = new HashMap<>(3);
            tokenMap.put(TokenConstant.LOGIN_DEVICE_WECHAT, TokenConstant.LOGIN_DEVICE_WECHAT);
            tokenMap.put(TokenConstant.JWT_USER_ID, sysWechatUser.getId().toString());
            tokenMap.put(TokenConstant.LOGIN_TIME, currentTime);
            String access_token = JwtUtil.createJwt(tokenMap, TokenConstant.EXPIRES_IN);
            String refresh_token = JwtUtil.createJwt(tokenMap, TokenConstant.REFRESH_EXPIRES_IN);
            // 缓存微信小程序用户信息，用于token验证 获取用户信息
            redisService.set(TokenConstant.LOGIN_USER_REDIS_KEY + TokenConstant.LOGIN_DEVICE_WECHAT + "_" + sysWechatUser.getId() + "_" + currentTime, wechatAppUser, TokenConstant.EXPIRES_IN);
            tokenVO.setAccess_token(access_token).setRefresh_token(refresh_token).setExpires_in(TokenConstant.EXPIRES_IN);
            return ResultUtil.success(tokenVO);
        } else {
            throw new ServiceException(Integer.valueOf(map.get("errcode").toString()), map.get("errmsg").toString());
        }
    }
}
